Skip to content

runtime/wasm: add WASI single-worker scheduler (based on #2192, #2208) - #2197

Draft
cpunion wants to merge 27 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-wasi-single-worker
Draft

runtime/wasm: add WASI single-worker scheduler (based on #2192, #2208)#2197
cpunion wants to merge 27 commits into
xgo-dev:mainfrom
cpunion:codex/wasm-wasi-single-worker

Conversation

@cpunion

@cpunion cpunion commented Jul 27, 2026

Copy link
Copy Markdown
Collaborator

Depends on #2192 and #2208.

Part of #2152.

Problem

GOOS=wasip1 GOARCH=wasm llgo build can produce a module after #2192, but goroutines still select the pthread runtime. The resulting executable imports shared host memory and WASI pthread facilities, so ordinary single-worker Wasmtime execution cannot run Go scheduler behavior.

The continuation implementation also needs a clear ownership boundary. Scheduler code should not know whether a backend uses Emscripten Fiber, a raw Asyncify save area, a future resumable ABI, or standardized stack switching.

Implementation

  • Make WASI pthread scheduling an explicit LLGO_WASI_THREADS=1 compatibility mode.
  • Add a raw wasm32 execution-context backend and a one-M/one-P FIFO scheduler for default WASI Preview1 builds.
  • Run package initialization and main.main as the first schedulable task while keeping the host entry on the system stack.
  • Put backend-specific stack and Asyncify storage ownership in runtime/internal/wasmcontext.
  • Let the runtime supply root-aware allocate/free callbacks only during cold context creation and destruction. The callbacks are not retained per G; Swap, Resume, and Suspend remain direct concrete hot-path calls.
  • Keep queue ownership and scheduling policy in runtime/internal/runtime; the continuation package does not inspect G/M/P state.
  • Declare Asyncify as a typed target capability; executable linking writes a temporary core module, runs pinned Binaryen instrumentation, and atomically publishes the final module. Archives and shared libraries are unchanged.
  • Compile LLVM 19 SjLj with legacy wasm EH, apply Asyncify, then translate the final module to standardized exnref EH.
  • Keep the existing JS wasm scheduler behavior behind the same internal context boundary.
  • Preserve the historical pthread source selection and fatal behavior when LLGO_WASI_THREADS=1 is set.
  • Depend on ssa/wasm: use static defer continuation dispatch #2208 for static wasm defer continuation dispatch; without it, LLVM 19 crashes while selecting runtime.EnsureLocalInitializer after compiler/runtime: add //llgo:tls and //llgo:gls package variables #2079.

The implementation is intentionally limited to the scheduler/context foundation. Channel blocking, timers and host async integration, wasm GC integration, root/safepoint work, and optional WASI Preview2 support remain follow-up work tracked by #2152.

Validation

All local commands use GOMAXPROCS=2 and -p=1 where applicable.

  • macOS arm64, Go 1.26.5: J32, J64, and P1 scheduler fixtures pass, including deadlock exit status and 5,000 serial goroutine lifecycles.
  • macOS: default P1 selects fatal_wasm + proc_wasip1; LLGO_WASI_THREADS=1 selects fatal_default + proc_pthread. Both build, and default P1 validates and runs under Wasmtime 39.0.1.
  • macOS: focused build/crosscompile/SSA tests, the complete runtime module, and native llgo test -p=1 ./test/llgoext pass.
  • runtime/internal/wasmcontext and runtime/internal/runqueue each have 100% statement coverage.
  • Ubuntu 24.04 amd64 container, capped below 15 GiB and at 2 CPUs with no network or swap: the changed runtime packages retain 100% coverage and both default/threads P1 source selections pass under Go 1.26.5.
  • Earlier branch validation also covers P1 -O0, -O3, ThinLTO, post-link validation/execution, no default pthread/shared-memory imports, the external-module _demo/c/helloc path, and the full Ubuntu/macOS CI matrix.
  • Full LTO still hits the existing LLVM 19 SelectionDAG crash in runtime.printany; the same failure reproduces on the unmodified runtime/wasm: add single-worker Asyncify scheduler #2192 base.

The lifecycle abstraction changes final scheduler modules by +2,991 bytes for J32 and +1,567 bytes for J64 against the same pre-lifecycle #2192/W tree. Binaryen reports six additional functions, two table entries, and seven indirect calls from the cold allocation callbacks. This is artifact size, not per-G memory; the existing 64 KiB stack and 64 KiB Asyncify reservations are unchanged. The measured P1 +172-byte integration delta uses an older W baseline and is not presented as a pure lifecycle comparison.

Independent diff over the current #2192 and #2208 merge tree: 31 files, +1,402/-101. The new lifecycle commit removes scheduler-owned backend buffers while adding focused storage failure/cleanup coverage. No previously runnable test is skipped or ignored.

@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.93252% with 5 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
internal/build/wasm_postlink.go 95.23% 2 Missing and 1 partial ⚠️
internal/build/build.go 85.71% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@cpunion cpunion changed the title runtime/wasm: add WASI single-worker scheduler (based on #2192) runtime/wasm: add WASI single-worker scheduler (based on #2192, #2208) Jul 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant